Skip to content

fix(connection): log routine event connection loss at DEBUG - #45

Merged
GreenGrassBlueOcean merged 4 commits into
OpenWebNet-HA:masterfrom
GreenGrassBlueOcean:fix/event-session-reconnect-debug-logging
Sep 22, 2026
Merged

GreenGrassBlueOcean merged 4 commits into
OpenWebNet-HA:masterfrom
GreenGrassBlueOcean:fix/event-session-reconnect-debug-logging

Conversation

@GreenGrassBlueOcean

@GreenGrassBlueOcean GreenGrassBlueOcean commented Sep 20, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Follows up on real-world testing feedback from @marcob79 in OpenWebNet-HA/MyHOME#304 and the review by @fedem95 below.

Gateways such as the MH200/MH201 enforce a hard ~1-hour session lifetime on the event connection. Every hour the gateway cleanly closes the socket; OWNEventSession.get_next() catches the resulting IncompleteReadError / ConnectionResetError and reconnects within milliseconds with zero frame loss. Because that path logged at WARNING, Home Assistant surfaced an alarming banner in Settings → System → Logs once an hour for a connection that was perfectly healthy.

Behaviour after this PR

Event Level
Single EOF / RST / socket error, reconnect succeeds DEBUG — "Event connection lost, reconnecting..."
DROP_BURST_COUNT (3) drops within DROP_BURST_WINDOW (10 min) WARNING — "Event connection dropped N times in 600s; network may be unstable.", repeated at most once per DROP_WARNING_INTERVAL (1 h)
asyncio.LimitOverrunError (over-long / garbage frame) WARNING — kept separate from the routine-drop path, as it is a protocol signal, not a session recycle
_reconnect() fails (Event session not connected / Reconnection failed; next attempt in …) WARNING — unchanged
Inactivity timeout, fatal negotiation / auth errors unchanged

Downstream integrations (MyHOME) keep their own 60 s availability grace period and still warn if an outage actually persists.

Changes

  • OWNd/connection.py
    • Routine event-session drops → DEBUG.
    • Sliding-window burst detector: 3 drops / 10 min → WARNING, rate-limited to once per hour. Thresholds are the new module constants DROP_BURST_COUNT, DROP_BURST_WINDOW, DROP_WARNING_INTERVAL next to RECONNECT_PAUSE*.
    • asyncio.LimitOverrunError split into its own except at WARNING.
  • tests/test_connection.py — clock-patched, deterministic tests for: routine drop at DEBUG, oversized frame at WARNING, burst on a freshly booted host, drops > 10 min apart staying at DEBUG, second warning after the hourly interval, and the tracker surviving a real _reconnect().

Fix for the CI failure on the previous push

The first burst implementation used 0.0 as the "never warned" sentinel and compared it against time.monotonic(), which is seconds since boot. now - 0.0 >= 3600 therefore meant "has this host been up for an hour" — true on a developer laptop and on @fedem95's 30 h MH201 test box, false on every fresh CI VM and on a Home Assistant host right after a reboot. The sentinel is now None, and test_event_connection_drop_burst_is_warning_on_fresh_host pins the clock at monotonic() = 100.0 so the regression cannot come back.

@codecov-commenter

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@fedem95 fedem95 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed on my physical MH201 (b13 + OWNd b8, running for 30 h): 30 event session drops, all recovered, zero "Reconnection failed" and zero availability warnings downstream. The demotion is justified.
On my hardware the reconnect occurs every ~57 min 40s.

Two suggestions for this PR:

  • a flapping session is now completely silent as long as each reconnect succeeds. Until we know the per-model cadence, a burst rule would be model-agnostic: warn when drops cluster, say three within ten minutes, then at most once an hour.
  • asyncio.LimitOverrunError shares this except block. An over-long or garbage frame is not a routine session recycle; hiding it at DEBUG loses a real protocol signal. I would keep that one at WARNING.

Failure paths still log properly (Event session not connected + Reconnection failed), tests pass locally and CI is green.

GreenGrassBlueOcean and others added 2 commits September 21, 2026 20:32
`_last_drop_warning` used `0.0` as its "never warned" sentinel and was
compared against `time.monotonic()`, which counts seconds since boot. So
`now - 0.0 >= 3600` really meant "has this host been up for an hour":
on a freshly booted machine (every CI runner, and a Home Assistant box
right after a reboot) the "dropped N times in 10 minutes" warning was
silently suppressed. This is why test_event_connection_drops_clustering
passed locally but failed on all four CI matrix entries.

- Use `None` as the sentinel.
- Lift 3 / 600 / 3600 into DROP_BURST_COUNT, DROP_BURST_WINDOW and
  DROP_WARNING_INTERVAL next to the other reconnect constants, and
  format the window into the log message instead of hard-coding it.
- Move the "covers EOF, RST, ..." comment back to the except clause it
  describes.
- Replace the wall-clock-dependent burst test with clock-patched tests
  covering: burst on a fresh host, drops spread over > 10 min staying
  at DEBUG, a second warning after DROP_WARNING_INTERVAL, and the
  tracker surviving a real `_reconnect()` (which goes through
  `connect()`). The clock patch replaces only `OWNd.connection.time`,
  so asyncio's own loop clock is untouched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@GreenGrassBlueOcean

Copy link
Copy Markdown
Contributor Author

@fedem95 thanks for the review and the 30 h MH201 run — both suggestions are in, and 5f24568 also fixes why the previous push went red in CI.

Why CI failed but local runs passed. The first burst implementation used 0.0 as the "never warned" sentinel and compared it to time.monotonic(), which is seconds since boot. now - 0.0 >= 3600 therefore meant "has this host been up for an hour" — true on a dev laptop and on your 30 h test box, false on every fresh CI VM. It would also have suppressed the burst warning for the first hour after a Home Assistant reboot, which is exactly when a flapping link is most likely. The sentinel is now None.

Burst rule (your first point): DROP_BURST_COUNT (3) drops within DROP_BURST_WINDOW (600 s) → one WARNING, then at most once per DROP_WARNING_INTERVAL (3600 s). Constants sit next to RECONNECT_PAUSE* and the window is formatted into the message. Your ~57m40s cadence stays at DEBUG: 10 drops 3460 s apart never accumulate past one entry in the window (covered by test_event_connection_drops_outside_window_stay_debug).

LimitOverrunError (your second point): its own except at WARNING, ahead of the routine-drop clause.

Tests are now clock-patched via a _fake_clock() helper that swaps only OWNd.connection.time — patching time.monotonic globally hijacks asyncio's loop clock. Five cases: routine drop at DEBUG, oversized frame at WARNING, burst on a fresh host (monotonic() = 100.0, fails with the old sentinel), drops > 10 min apart, second warning after the hourly interval, and the tracker surviving a real _reconnect() → connect() round-trip (the earlier tests mocked _reconnect, which would have hidden a reset in connect()).

All 8 checks green; connection.py stays at 100 % line coverage.

fedem95
fedem95 previously approved these changes Sep 21, 2026

@fedem95 fedem95 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test_event_connection_drop_tracker_survives_reconnect is a nice addition, mocking _reconnect in the other tests would have hidden a reset inside connect().

One tiny thing, non-blocking: the warning always reads "dropped 3 times in 600s" even when the three drops happened five seconds apart, which reads as ten minutes of trouble. Passing the actual span (now - self._recent_drops[0]) would be more informative.

Approving

Per review: the warning always read "dropped 3 times in 600s" because it
formatted the measurement window rather than the observed span, so three
drops five seconds apart looked like ten minutes of trouble. Log
`now - self._recent_drops[0]` instead, which is the span the drops in the
current window actually covered.

Adds test_event_connection_drop_burst_reports_real_span (a burst spread
over 400s reports 400s), and the two existing burst assertions now pin
the real 1.0s span instead of the window constant.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@fedem95 fedem95 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Span fix is in and it reads correctly now — I ran it: three drops five seconds apart log "dropped 3 times in 5s", three spread over 400 s log "in 400s". The dedicated test for the slow burst is a good addition.
Is ok for me

@GreenGrassBlueOcean
GreenGrassBlueOcean merged commit abe1e1c into OpenWebNet-HA:master Sep 22, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants